/**
 * 爬虫 模型。从时光网的影人库中抓取数据。
 */
const constants = require('../../utils/constants');
const models = require('../../utils/models');
const fileUtils = require('../../utils/fileUtils');
const path = require('path');
const fs = require('fs');
const request = require('request');
const Phantom = require('phantom');

let instance = null;

const searchUrl = 'http://movie.mtime.com/people/search/focus/#sortType=4&listType=0&r={region}&pageIndex={page}';
const Regions = {
    Mainland: {
        value: 1,
        pageCount: 464
    },
    HK: {
        value: 2,
        pageCount: 119
    },
    West: {
        value: 3,
        pageCount: 411
    },
    East: {
        value: 4,
        pageCount: 142
    }
};

const RegisterStatus = {
  Init: 0,
  Success: 1,
  Error: 2,
  Ignored: 3
};

// 搜索结果元素选择器
const linkSelector = '#resultListRegion .table p a';

// 明星信息元素选择器
const chNameSelector = '.per_header h2';
const enNameSelector = '.per_header .enname';
const perCoverSelector = '#personDetailRegion img';

module.exports = class extends think.Model {
    get tableName() {
        return 'crawl_star';
    }

    async start() {
        for (let i = 464; i <= Regions.Mainland.pageCount; ++i) {
            let searchPage = await this.getPhantomPage(true);

            let url = searchUrl.replace('{region}', Regions.Mainland.value).replace('{page}', i);
            const status = await searchPage.open(url);
            if (status === 'fail') {
                think.logger.error('搜索页面打开失败: ');
            }

            // 获取搜索结果页面
            const links = await searchPage.evaluate(function (linkSelector) {
                var links = [];
                var elements = document.querySelectorAll(linkSelector);
                for (var idx = 0; idx < elements.length; ++idx) {
                    var elem = elements[idx];
                    links.push(elem.href);
                }

                return links;
            }, linkSelector);

            // 遍历链接，获取里面的列表数据
            let artPage = await this.getPhantomPage();
            for (let j = 0; j < links.length; ++j) {
                const profileUrl = links[j];
                const mId = 'm_' + /(\d+)/.exec(profileUrl)[0];
                const crawled = await this.isStarCrawled(mId);
                if (crawled) {
                    think.logger.info(`第${i}页，第${j}条数据已采集，[忽略]。资料首页：${profileUrl}`);
                    continue;
                }

                think.logger.info(`正在采集第${i}页，第${j}条数据，资料首页：${profileUrl}`);

                await artPage.open(profileUrl);
                const person = await artPage.evaluate(function (chNameSelector, enNameSelector, perCoverSelector) {
                    var enNameElem = document.querySelector(enNameSelector);
                    var chName = document.querySelector(chNameSelector).innerText;
                    var enName = enNameElem ? enNameElem.innerText : '';
                    var cover = document.querySelector(perCoverSelector).src;

                    return {
                        chName: chName,
                        enName: enName,
                        cover: cover,
                    }
                }, chNameSelector, enNameSelector, perCoverSelector);
                person.profileUrl = profileUrl;
                person.mId = mId;
                person.page = i;
                person.offset = j;

                /*/ 封面为空，忽略此明星信息
                if (person.cover.indexOf('290X440X4') > 0) {
                    think.logger.info(`找不到头像/写真，忽略此明星。`);
                    continue;
                }*/

                // 下载封面照片，并保存抓取记录
                const imgData = await new Promise(function (resolve, reject) {
                    return resolve(request({url: person.cover}));
                });

                person.localFilePath = await new Promise(function (resolve, reject) {
                    const photoPath = path.join(think.ROOT_PATH, '/www/static/miniapp/images/faces/stars', mId + '.jpg');
                    const stream  = imgData.pipe(fs.createWriteStream(photoPath));

                    stream.on('finish', function () {
                        resolve(photoPath);
                    });
                    stream.on('error', function (err) {
                        think.logger.error(err.message);
                    });
                });

                await this.createOne(person);
            }

            await searchPage.close();
            await artPage.close();
        }
    }

    /**
     * 判断明星数据是否已经抓取过。
     */
    async isStarCrawled(id) {
        const count = await this.where({id: id}).count();
        return count > 0;
    }

    /**
     * 保存抓取记录。同时保存脸部数据。
     */
    async createOne(data) {
        try {
            await this.startTrans();

            // 保存抓取记录
            await this.add({
                id: data.mId,
                profileUrl: data.profileUrl,
                chName: data.chName,
                enName: data.enName,
                page: data.page,
                offset: data.offset
            }, {});

            // 保存照片记录
            const userId = data.mId;
            const bid = userId;
            const photoModel = think.model(models.Photo).db(this.db());
            await photoModel.createOne({
                userId: userId,
                userName: data.chName,
                photoType: constants.PhotoType.Star,
                bid: bid,
                bUserInfo: data.chName,
                localFilePath: data.localFilePath,
                photoUrl: data.cover
            });
            
            await this.commit();
        } catch (err) {
            think.logger.error('保存抓取记录出错：' + err.message);
            
            await this.rollback();
        }
    }

    /**
     * 重启phantomJs
     */
    async getPhantomPage(restart) {
        if (restart && instance) instance.exit();

        instance = await Phantom.create(['--ignore-ssl-errors=yes', '--load-images=no']);
        return instance.createPage();
    }

    /**
     * 注册明星脸型。为防止百度识别的API调用超过限额，每次仅获取5个注册。
     */
    async registerStars() {
        const list = await this.where({registered: RegisterStatus.Error}).order('createTime asc').limit(1).select();
        if (think.isEmpty(list)) return;

        const photoModel = think.model(models.Photo);
        const bdAppModel = think.model(models.VendorBaiduApp);
        const bdApp = await bdAppModel.getOneByWxAppId('wxc57b16169a279166');

        for (let i = 0; i < list.length; ++i) {
            let item = list[i];

            try {
                let photo = await photoModel.getOneByBid(item.id);

                // 照片不存在，忽略
                if (!fileUtils.existsSync(photo.photoPath)) {
                    await this.where({id: item.id}).update({registered: RegisterStatus.Ignored, notes: '本地脸型照片找不到，忽略!'});
                    continue;
                }

                let bid = photo.bid;
                let bUserInfo = photo.bUserInfo;
                let registerGroup = 'stars';
                let actionType = 'replace';
                let photoData = fs.readFileSync(photo.photoPath).toString("base64");

                const faceMgr = think.model(models.VendorBaiduFaceManager);
                await faceMgr.register(bid, bUserInfo, registerGroup, photoData, actionType, bdApp);

                // 标记为已注册
                await this.where({id: item.id}).update({registered: RegisterStatus.Success, notes: null});
            } catch (err) {
                think.logger.error('脸型注册失败，bid: ' + item.id);

                await this.where({id: item.id}).update({registered: RegisterStatus.Error, notes: err.message});
            }
        }
    }

    /**
     * 过滤无效记录。
     */
    async filterinvalidData() {
        const photoModel = think.model(models.Photo);
        const list = await this.field('id').where({registered: RegisterStatus.Init}).order('createTime asc').select();

        for(let i = 0; i < list.length; ++i) {
            const item = list[i];
            let photo = await photoModel.getOneByBid(item.id);

            if (!fileUtils.existsSync(photo.photoPath)) {
                await this.where({id: item.id}).update({registered: RegisterStatus.Ignored, notes: '本地脸型照片找不到，忽略!'});
            }
        }
    }
};
